﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using XNADash.BoardBlocks;
using XNADash.Sound;

namespace XNADash
{
    public class DashBoard
    {
        public const int BOARDSIZEX = 20;
        public const int BOARDSIZEY = 12;
        public const int VISIBLEMARGINX = 8;
        public const int VISIBLEMARGINY = 5;
        public const int BLOCKSIZE  = 40;

        public int HeartsToComplete { get; private set; }
        public int HeartsEaten { get; set; }

        public string LevelName { get; set; }
        public string LevelAuthor { get; set; }

        public IEnumerable<BaseBlock> Blocks
        {
            get
            {
                return BlocksIndexed.SelectMany( dX => dX.Value ).SelectMany( dY => dY.Value );
            }
        }

        private Dictionary<int, Dictionary<int, List<BaseBlock>>> BlocksIndexed = new Dictionary<int, Dictionary<int, List<BaseBlock>>>();

        public List<BaseBlock> this[int Row, int Col]
        {
            get
            {
                this.EnsureSlotForBlockExists( Row, Col );
                return BlocksIndexed[Row][Col];
            }
        }

        private void EnsureSlotForBlockExists( int Row, int Col )
        {
            if ( !BlocksIndexed.ContainsKey( Row ) )
                BlocksIndexed.Add( Row, new Dictionary<int, List<BaseBlock>>() );

            if ( !BlocksIndexed[Row].ContainsKey( Col ) )
                BlocksIndexed[Row].Add( Col, new List<BaseBlock>() );
        }

        public DashBoard()
        {
            HeartsToComplete = 0;
        }

        public bool MustRestart
        {
            get
            {
                if ( this.Blocks.Any( b => b is BoomBlock ) )
                    return false;

                if ( this.Blocks.All( b => !(b is PlayerBlock ) ) )
                    return true;

                if ( this.Blocks.All( b => !( b is ExitBlock ) ) )
                    return true;

                return false;
            }
        }

        public bool Completed
        {
            get
            {
                PlayerBlock player = this.Blocks.OfType<PlayerBlock>().FirstOrDefault();
                if ( player == null ) return false;

                return this.Blocks.Any( b => ( b is ExitBlock ) && b.X == player.X && b.Y == player.Y );
            }
        }

        public void AddBlock( BaseBlock Block )
        {
            Block.Board = this;

            if ( Block is HeartBlock )
                HeartsToComplete++;

            this[Block.X,Block.Y].Add( Block );
        }

        public void RemoveBlock( BaseBlock Block )
        {
            this[Block.X,Block.Y].Remove( Block );
        }

        public bool UpdateBoard( GameTime gameTime, KeyboardState state )
        {
            foreach ( var block in Blocks )
                block.Moved = false;

            foreach ( int col in Enumerable.Range( 0, this.Width+2 ) )
                foreach ( int row in Enumerable.Range( 0, this.Height+2 ).Reverse() )
                {
                    var block = this[col, row].FirstOrDefault();
                    if ( block != null && !block.Moved )
                        block.ApplyPhysics();
                }

            return true;
        }

        public void ExplodePlayer()
        {
            PlayerBlock player = Blocks.OfType<PlayerBlock>().FirstOrDefault();

            if ( player != null )
            {
                player.ExplodeNeighbour( Directions.None );
                SoundFactory.Instance.RegisterEffect( SoundType.Bomb );
            }
        }

        public bool UpdatePlayer( Directions Direction )
        {
            PlayerBlock player = Blocks.OfType<PlayerBlock>().FirstOrDefault();

            if ( player != null )
                player.UpdatePosition( this, Direction );

            if ( this.HeartsToComplete == this.HeartsEaten )
            {
                this.Blocks.OfType<ExitBlock>().ToList().ForEach( e => e.Open() );
            }

            return true;
        }

        private int? _width;
        public int Width
        {
            get
            {
                if ( _width == null )
                    _width = Blocks.Max( b => b.X );

                return _width.Value;
            }
        }

        private int? _height;
        public int Height
        {
            get
            {
                if ( _height == null )
                    _height = Blocks.Max( b => b.Y );

                return _height.Value;
            }
        }

        PlayerBlock lastLivePlayer;
        public void DrawBoard( 
            SpriteBatch spriteBatch, 
            SpriteFont font, 
            int FrameNumber )
        {
            if ( this.Player != null )
                lastLivePlayer = this.Player;

            int StartX = Math.Max( 0, Math.Min( Math.Max( 0, lastLivePlayer.X - VISIBLEMARGINX ), Width  - BOARDSIZEX + 1 ) );
            int StartY = Math.Max( 0, Math.Min( Math.Max( 0, lastLivePlayer.Y - VISIBLEMARGINY ), Height - BOARDSIZEY + 1 ) );

            foreach ( var block in Blocks.Where( b => b.X >= StartX && b.X < StartX + BOARDSIZEX && b.Y >= StartY && b.Y < StartY + BOARDSIZEY ) )
            {
                spriteBatch.Draw( block.Texture, new Rectangle( (block.X-StartX) * BLOCKSIZE, (block.Y-StartY) * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE ), Color.White );
            }
        }

        private PlayerBlock Player
        {
            get
            {
                return this.Blocks.OfType<PlayerBlock>().FirstOrDefault();
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            foreach ( int row in Enumerable.Range( 0, BOARDSIZEY ) )            
            {
                foreach ( int col in Enumerable.Range( 0, BOARDSIZEX ) )
                {
                    BaseBlock block = this.Blocks.Where( b => b.Y == row && b.X == col ).FirstOrDefault();
                    if ( block != null )
                        sb.Append( block.ToString() );
                    else
                        sb.Append( " " );
                }

                sb.Append( "\r\n" );
            }

            return sb.ToString();
        }
    }
}
